package org.dreamwork.peony;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by IntelliJ IDEA.
 * User: seth
 * Date: 2009-9-10
 * Time: 16:39:18
 */
public class ReflectionUtil {
    private Class type;
    private Object target;

    private Map<String, Method> getters;
    private Map<String, Method> setters;

    private static final Map<Class, Map<String, Map<String, Method>>> pool =
            new ConcurrentHashMap<Class, Map<String, Map<String, Method>>> ();

    private static final Lock lock = new ReentrantLock ();

    public ReflectionUtil (Object target) {
        this.target = target;
        this.type = target.getClass ();
        fetchAccessors ();
    }

    public Method getSetter (String name) {
        return setters.get (name.toLowerCase ().trim ());
    }

    public Method getGetter (String name) {
        return getters.get (name.toLowerCase ().trim ());
    }

    public Object getValue (String name) {
        try {
            Method getter = getGetter (name);
            if (getter != null)
                return getter.invoke (target);
            try {
                Field field = type.getDeclaredField (name);
                field.setAccessible (true);
                return field.get (target);
            } catch (NoSuchFieldException nsfe) {
                throw new RuntimeException (nsfe);
            }
        } catch (Exception ex) {
            return null;
        }
    }

    @SuppressWarnings ("unchecked")
    public void setAttributes (Map<String, String> attributes) throws InvocationTargetException, IllegalAccessException {
        for (String name : attributes.keySet ()) {
            String value = attributes.get (name);
            Method method = getSetter (name);
            if (method != null) {
                Class paramType = method.getParameterTypes () [0];
                if (paramType == Integer.class || paramType == int.class)
                    method.invoke (target, Integer.valueOf (value));
                else if (paramType == Long.class || paramType == long.class)
                    method.invoke (target, Long.valueOf (value));
                else if (paramType == Short.class || paramType == short.class)
                    method.invoke (target, Short.valueOf (value));
                else if (paramType == Byte.class || paramType == byte.class)
                    method.invoke (target, Byte.valueOf (value));
                else if (paramType == Boolean.class || paramType == boolean.class)
                    method.invoke (target, Boolean.valueOf (value));
                else if (paramType == Character.class || paramType == char.class)
                    method.invoke (target, value.charAt (0));
                else if (paramType == Float.class || paramType == float.class)
                    method.invoke (target, Float.valueOf (value));
                else if (paramType == Double.class || paramType == double.class)
                    method.invoke (target, Double.valueOf (value));
                else if (paramType.isAssignableFrom (CharSequence.class))
                    method.invoke (target, value);
            }
        }
    }

    private void fetchAccessors () {
        if (setters != null) return;
        Map<String, Map<String, Method>> tmp;
        try {
            lock.lock ();
            tmp = pool.get (type);
            if (tmp != null) {
                setters = pool.get (type).get ("setter");
                getters = pool.get (type).get ("getter");
                if (setters != null)
                    return;
            }
        } finally {
            lock.unlock ();
        }

        setters = new HashMap<String, Method> ();
        getters = new HashMap<String, Method> ();
        try {
            lock.lock ();
            tmp = new ConcurrentHashMap<String, Map<String, Method>> ();
            pool.put (type, tmp);
            tmp.put ("setter", setters);
            tmp.put ("getter", getters);
        } finally {
            lock.unlock ();
        }
        Method[] methods = type.getMethods ();
        for (Method method : methods) {
            String name = method.getName ();
            String key = translateName (name);
            if (name.startsWith ("set")) {
                setters.put (key, method);
            } else if (name.startsWith ("get") && !name.equals ("getClass")) {
                getters.put (key, method);
            }
            method.setAccessible (true);
        }
    }

    private String translateName (String name) {
        return name.toLowerCase ().trim ().substring (3);
    }
}